package com.gitee.ixtf.vertx.internal

import graphql.schema.DataFetcher
import graphql.schema.DataFetchingEnvironment
import graphql.schema.GraphQLList
import io.vertx.core.Future
import io.vertx.core.buffer.Buffer
import io.vertx.ext.web.RoutingContext
import io.vertx.kotlin.coroutines.coAwait
import java.util.*
import java.util.concurrent.CompletionStage
import kotlin.reflect.full.callSuspend
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.reactive.asPublisher
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kotlinx.coroutines.reactor.mono
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

fun DataFetchingEnvironment.rc(): RoutingContext = graphQlContext.get(RoutingContext::class.java)

private suspend fun DataFetchingEnvironment.convertInvoke(o: Any?): Any? =
    when (o) {
      is Buffer -> if (fieldDefinition.type is GraphQLList) o.toJsonArray() else o.toJsonObject()
      is ByteArray -> convertInvoke(Buffer.buffer(o))
      is Future<*> -> convertInvoke(o.coAwait())
      is CompletionStage<*> -> convertInvoke(Future.fromCompletionStage(o))
      is Optional<*> -> convertInvoke(o.orElse(null))
      is Flow<*> -> o.toList()
      is Mono<*> -> convertInvoke(o.awaitSingleOrNull())
      is Flux<*> -> convertInvoke(o.collectList().awaitSingle())
      else -> o
    }

private suspend fun DataFetchingEnvironment.convertInvokeToPublisher(o: Any?): Publisher<*> =
    when (o) {
      is Mono<*> -> o
      is Flux<*> -> o
      is Flow<*> -> (o as Flow<Any>).asPublisher()
      is Future<*> -> convertInvokeToPublisher(o.coAwait())
      is CompletionStage<*> -> convertInvokeToPublisher(Future.fromCompletionStage(o))
      else -> Mono.justOrEmpty(o)
    }

open class DefaultDataFetcher(o: GraphqlMethod) : ApiMethodHandler(o), DataFetcher<Any> {
  override fun get(env: DataFetchingEnvironment): Mono<Any> =
      mono {
            val args = converters.map { it.convert(env) }.toTypedArray()
            val invoke = method.invoke(instance, *args)
            env.convertInvoke(invoke)
          }
          .doOnError { log.error("", it) }
}

open class SuspendDataFetcher(o: GraphqlMethod) : ApiMethodHandler(o), DataFetcher<Any> {
  override fun get(env: DataFetchingEnvironment): Mono<Any> =
      mono {
            val args = converters.map { it.convert(env) }.toTypedArray()
            val invoke = kotlinFunction.callSuspend(instance, *args)
            env.convertInvoke(invoke)
          }
          .doOnError { log.error("", it) }
}

class DefaultMutationDataFetcher(o: MutationMethod) : DefaultDataFetcher(o)

class SuspendMutationDataFetcher(o: MutationMethod) : SuspendDataFetcher(o)

class DefaultQueryDataFetcher(o: QueryMethod) : DefaultDataFetcher(o)

class SuspendQueryDataFetcher(o: QueryMethod) : SuspendDataFetcher(o)

class DefaultSourceDataFetcher(o: SourceMethod) : DefaultDataFetcher(o)

class SuspendSourceDataFetcher(o: SourceMethod) : SuspendDataFetcher(o)

class DefaultSubscriptionDataFetcher(o: SubscriptionMethod) :
    ApiMethodHandler(o), DataFetcher<Publisher<*>> {
  override fun get(env: DataFetchingEnvironment): Publisher<*> =
      mono {
            val args = converters.map { it.convert(env) }.toTypedArray()
            val invoke = method.invoke(instance, *args)
            env.convertInvokeToPublisher(invoke)
          }
          .flatMapMany { it }
          .doOnError { log.error("", it) }
}

class SuspendSubscriptionDataFetcher(o: SubscriptionMethod) :
    ApiMethodHandler(o), DataFetcher<Publisher<*>> {
  override fun get(env: DataFetchingEnvironment): Publisher<*> =
      mono {
            val args = converters.map { it.convert(env) }.toTypedArray()
            val invoke = kotlinFunction.callSuspend(instance, *args)
            env.convertInvokeToPublisher(invoke)
          }
          .flatMapMany { it }
          .doOnError { log.error("", it) }
}
